﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml.Linq;
using Microsoft.Devices;
using Microsoft.Phone.BackgroundAudio;
using Microsoft.Phone.Info;
using Microsoft.Phone.Shell;
using Samples.Radio.Agent.Data;
using Samples.Radio.Agent.Model;
using WindowsPhoneAudioStreaming.Audio.Streaming;

namespace Samples.Radio.Agent
{
    public class AbsoluteRadioPlayingService : INowPlayingService
    {
        //private string tileFileName = "/Shared/ShellContent/backgroundart.jpg";
        private const int MaximumPlaylistTrackHistoryItemCount = 10;

        #region Properties

        public Uri CurrentDataService
        {
            get;
            set;
        }

        public int NumberRecentTracksToDisplay
        {
            get;
            set;
        }

        public DateTime LastUpdated
        {
            get;
            private set;
        }

        /// <summary>
        /// Current playing track
        /// </summary>
        public TrackModel CurrentTrack
        {
            get;
            private set;
        }

        public Repository Repository { get; private set; }

        /// <summary>
        /// Recently played tracks
        /// </summary>
        private readonly List<TrackModel> recentTracks;
        public TrackModel[] RecentTracks
        {
            get { return recentTracks.ToArray(); }
        }

        private readonly List<PlaylistTrack> activeTrackList;
        public PlaylistTrack[] ActiveTrackList
        {
            get
            {
                //Get the latest from the database
                GetCurrentPlaylist();
                return activeTrackList.ToArray();
            }
        }

        public PlaylistTrack[] QueuedTrackList
        {
            get
            {
                using(var wrapper =new MutexWrapper())
                {
                    return wrapper.Database.Playlists
                    .Select(p => new PlaylistTrack
                                     {
                        Album = p.Album,
                        Artist = p.Artist,
                        Title = p.Title,
                        AlbumArtUrl = p.AlbumArtUrl,
                        Source = p.Source
                    }).ToArray();
                }
            }
        }

        #endregion

        #region Constructors
        public AbsoluteRadioPlayingService( Repository repository)
        {
            Repository = repository;
            activeTrackList = new List<PlaylistTrack>();
            CurrentTrack = new TrackModel();
            recentTracks= new List<TrackModel>();
            NumberRecentTracksToDisplay = 1;

            CurrentDataService = new Uri(Repository.Configuration.NowPlayingUrl);

            // This is called as part of retrieving playlist, so no need to call here
            //GetCurrentPlaylist();
        }
        #endregion

        #region Methods

        /// <summary>
        /// Get the latest track and update the recent tracks list
        /// </summary>
        public void RefreshCurrentTrack()
        {
            if (CurrentDataService != null && 
                !string.IsNullOrEmpty(CurrentDataService.AbsoluteUri))
            {
                var client = new WebClient();
                client.OpenReadCompleted += (sender, e) =>
                {
                    if (e.Error != null)
                        return;

                    try
                    {
                        var strm = e.Result;
                        while (strm.ReadByte() != 60)
                        {

                        }
                        strm.Position -= 1;
                        var nowPlayingXml = XDocument.Load(strm);

                        var nowPlayingData = from query in nowPlayingXml.Descendants("track")
                                             select new TrackModel
                                                        {
                                                            Title = (string) query.Element("name"),
                                                            Album = "",
                                                            TrackImageUrl = (string) query.Element("image"),
                                                            Artist = (string) query.Element("artist"),
                                                            PlayedTime =
                                                                DateTime.Parse((string) query.Element("played"))
                                                        };

                        if (nowPlayingData.Count() >= NumberRecentTracksToDisplay)
                        {
                            recentTracks.Clear();
                        }

                        bool isFirstTrack = true;
                        foreach (var track in nowPlayingData)
                        {
                            if (!string.IsNullOrEmpty(track.Title))
                            {
                                if (track.NowPlaying || CurrentTrack == null || CurrentTrack.Title == "" ||
                                    isFirstTrack)
                                {
                                    CurrentTrack = track;
                                    isFirstTrack = false;
                                }
                                else if (!recentTracks.Contains(track))
                                {
                                    if (recentTracks.Count >= NumberRecentTracksToDisplay)
                                    {
                                        recentTracks.RemoveAt(recentTracks.Count - 1);
                                    }

                                    recentTracks.Add(track);
                                }
                            }
                        }

                        // close 
                        strm.Close();

                        LastUpdated = DateTime.Now;

                        if (TracksUpdated != null)
                        {
                            TracksUpdated(this, null);
                        }
                    }
                    catch(Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                    }

                };
                var uniqueUri = new Uri(CurrentDataService.AbsoluteUri + "?r=" + Guid.NewGuid(), UriKind.Absolute);
                client.OpenReadAsync(uniqueUri);

            }
        }

        /// <summary>
        /// Refresh the current playlist
        /// </summary>
        public void GetCurrentPlaylist()
        {
            using (var wrapper = new MutexWrapper())
            {
                LoadCurrentPlayList(wrapper.Database);
            }
        }

        public byte[] EncodeToJpeg(WriteableBitmap wb)
        {
            using (var stream = new MemoryStream())
            {
                wb.SaveJpeg(stream, wb.PixelWidth, wb.PixelHeight, 0, 85);
                return stream.ToArray();
            }
        }

        public IPlaybackOptions PlaybackOptions { get{return new StreamOptions();} }

        public int NumberOfTracks
        {
            get { return ActiveTrackList.Length; }
        }

        public AudioTrack TrackByIndex(int trackIndex)
        {
            if (ActiveTrackList != null && ActiveTrackList.Length > 0)
            {
                return ActiveTrackList[trackIndex].ToAudioTrack();
            }
            return null;
        }

        public int NextTrackIndex(int currentTrackIndex)
        {
            if (currentTrackIndex < ActiveTrackList.Length - 1)
            {
                currentTrackIndex += 1;
            }
            else
            {
                currentTrackIndex = -1;
            }
            return currentTrackIndex;
        }

        public int PreviousTrackIndex(int currentTrackIndex)
        {
            if (currentTrackIndex >0)
            {
                currentTrackIndex -= 1;
            }
            else
            {
                currentTrackIndex = -1;
            }
            return currentTrackIndex;
        }

        /// <summary>
        /// Update the current playlist
        /// </summary>
        public void UpdatePlaylist(bool includeMusicAndVideoHub = false)
        {
            var currentSetup = Repository.Configuration;

            if (currentSetup != null &&
                !string.IsNullOrWhiteSpace(currentSetup.NowPlayingUrl))
            {
                CurrentDataService = new Uri(currentSetup.NowPlayingUrl);
                TracksUpdated += (s, e) =>
                {
                    UpdateLiveTile();

                    // If debugging is enabled, launch the agent again in one minute.
                    LiveTileUpdated += (s1, e1) =>
                    {
                        //Successfully completed updating the live tile..
                    };
                };
                RefreshCurrentTrack();
            }

        }

        /// <summary>
        /// Create a live tile update
        /// </summary>
        public void UpdateLiveTile(bool includeMusicAndVideoHub=false)
        {
            //Deployment.Current.Dispatcher.BeginInvoke(() =>
            //{
                UpdateTracksToDatabase();

                var updatedTile = new StandardTileData
                                      {
                    BackTitle = CurrentTrack.Artist, //String.Format("{0} - {1}", this.CurrentTrack.Title, this.CurrentTrack.Artist),
                    BackContent = CurrentTrack.Title
                };


                if (CurrentTrack != null && !String.IsNullOrEmpty(CurrentTrack.TrackImageUrl))
                {
                    var backgroundImage = new Uri(CurrentTrack.TrackImageUrl, UriKind.Absolute);
                    updatedTile.BackBackgroundImage = backgroundImage;
                }

            var primaryTile = ShellTile.ActiveTiles.First();
                primaryTile.Update(updatedTile);

                if (includeMusicAndVideoHub)
                {
                    LoadNewArt(CurrentTrack, CurrentTrack.TrackImageUrl);
                }

            if (LiveTileUpdated != null)
                {
                    LiveTileUpdated(this, null);
                }
           // });

        }

        private static int updatingMusicVideoTile;
        private void LoadNewArt(TrackModel track, string art)
        {
            if (Interlocked.CompareExchange(ref updatingMusicVideoTile, 1, 0) == 1) return;
            var wc = new WebClient();
            wc.OpenReadCompleted += OnWebReadCompleted;
            wc.OpenReadAsync(new Uri(art), track);
            //Waiter.WaitOne();
        }

        private static readonly object ReadwriteLock = new object();
        private static void OnWebReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            try
            {

                if (e.Error == null)
                {
                    var track = e.UserState as TrackModel;

                    if (track == null)
                    {
                        return;
                    }

                    
                     const string tileFileName = "/Shared/ShellContent/backgroundart.jpg";
                     lock (ReadwriteLock)
                     {
                         using (
                             var isoFile = IsolatedStorageFile.GetUserStoreForApplication().OpenFile(tileFileName,
                                                                                                     FileMode.Create,
                                                                                                     FileAccess.
                                                                                                         ReadWrite))
                         {
                             e.Result.CopyTo(isoFile);
                         }
                     }

                    UpdateMusicAndVideoHubTile();
                    //Deployment.Current.Dispatcher.BeginInvoke(() =>
                    //{
                        //try
                        //{
                        //    DebugOutputMemoryUsage();
                        //    lock (readwriteLock)
                        //    {
                        //        using (
                        //            var isoFile = IsolatedStorageFile.GetUserStoreForApplication().OpenFile(
                        //                tileFileName,
                        //                FileMode.Open,
                        //                FileAccess.Read)
                        //            )
                        //        {
                        //            var img = new WriteableBitmap(173,173);
                        //            img.SetSource(isoFile);
                        //            img.Invalidate();
                        //            using (var ms = new MemoryStream())
                        //            {
                        //                img.SaveJpeg(ms, 173, 173, 0, 70);
                        //                ms.Flush();
                        //                ms.Seek(0, SeekOrigin.Begin);
                        //                var mediaHistoryItem = new MediaHistoryItem { ImageStream = ms, Title = track.Title };
                        //                mediaHistoryItem.PlayerContext["Title"] = track.Title;
                        //                MediaHistory.Instance.NowPlaying = mediaHistoryItem;
                        //            }

                                    
                        //        }
                        //    }
                        //}
                        //catch(Exception ex)
                        //{
                        //    // Ignore and continue
                        //    Debug.WriteLine(ex.Message);
                        //}
                    //});


                   
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
            finally
            {
                Interlocked.Exchange(ref updatingMusicVideoTile, 0);
            }
        }


        private static void UpdateMusicAndVideoHubTile()
        {
            //var waiter = new ManualResetEvent(false);
            Deployment.Current.Dispatcher.BeginInvoke(delegate
            {
                try
                {
                    const string tileFileName = "/Shared/ShellContent/backgroundart.jpg";
                    using (
                        var isoFile = IsolatedStorageFile.
                            GetUserStoreForApplication().OpenFile(
                                tileFileName,
                                FileMode.Open,
                                FileAccess.Read)
                        )
                    {
                        var bimg = new BitmapImage();
                        bimg.SetSource(isoFile);

                        var img = new Image
                                      {
                                          Source = bimg,
                                          Stretch = Stretch.UniformToFill,
                                          HorizontalAlignment = HorizontalAlignment.Center,
                                          VerticalAlignment = VerticalAlignment.Center,
                                          Height = 358,
                                          Width = 358
                                      };

                        img.Measure(new Size(358, 358));
                        img.Arrange(new Rect(0, 0, 358, 358));

                        var wbmp = new WriteableBitmap(358, 358);
                        wbmp.Render(img, new CompositeTransform());
                        //wbmp.SetSource(isoFile);
                        wbmp.Invalidate();

                        const string tileFileName2 = "/Shared/Media/backgroundart_x.jpg";
                        using (
                        var isoFile2 = IsolatedStorageFile.
                            GetUserStoreForApplication().OpenFile(
                                tileFileName2,
                                FileMode.Create,
                                FileAccess.Write)
                        )
                        {
                            wbmp.SaveJpeg(isoFile2,358,358,0,70);
                            //using (var strm = ei.GetStream())
                            //{
                            //    strm.CopyTo(ms);
                            //}

                            //EncodeJpeg(wbmp, isoFile2);
                        }

                        //var ei = EditableImage.FromBitmapImage(wbmp);
                        using (var x = IsolatedStorageFile.
                            GetUserStoreForApplication().OpenFile(
                                tileFileName2,
                                FileMode.Open,
                                FileAccess.Read))
                        {
                            var ms =new MemoryStream();
                            x.CopyTo(ms);
                            


                            ms.Flush();
                            ms.Seek(0, SeekOrigin.Begin);
                            var mediaHistoryItem = new MediaHistoryItem
                                                       {ImageStream = ms, Title = "Live stream", Source = ""};
                            mediaHistoryItem.PlayerContext["Title"] =
                                "Live stream";
                            MediaHistory.Instance.NowPlaying =
                                mediaHistoryItem;
                        }

                        //using (var ms = new MemoryStream())
                        //{
                        //    img.SaveJpeg(ms, 173, 173, 0, 70);
                        //    ms.Flush();
                        //    ms.Seek(0, SeekOrigin.Begin);
                        //    var mediaHistoryItem = new MediaHistoryItem { ImageStream = ms, Title = "Live stream" };
                        //    mediaHistoryItem.PlayerContext["Title"] = "Live stream";
                        //    MediaHistory.Instance.NowPlaying = mediaHistoryItem;
                        //}


                    }
                }
                catch (Exception ex)
                {
                    // Ignore and continue
                    Debug.WriteLine(ex.Message);
                }
            });
          //  waiter.WaitOne();
        }




        //public static void EncodeJpeg(WriteableBitmap bmp, Stream destinationStream)
        //{
        //    // Init buffer in FluxJpeg format   
        //    int w = bmp.PixelWidth;
        //    int h = bmp.PixelHeight;
        //    int[] p = bmp.Pixels;
        //    byte[][,] pixelsForJpeg = new byte[3][,];
        //    // RGB colors   
        //    pixelsForJpeg[0] = new byte[w, h];
        //    pixelsForJpeg[1] = new byte[w, h];
        //    pixelsForJpeg[2] = new byte[w, h];
        //    // Copy WriteableBitmap data into buffer for FluxJpeg   
        //    int i = 0;
        //    for (int y = 0; y < h; y++)
        //    {
        //        for (int x = 0; x < w; x++)
        //        {
        //            int color = p[i++];
        //            pixelsForJpeg[0][x, y] = (byte)(color >> 16);
        //            // R        
        //            pixelsForJpeg[1][x, y] = (byte)(color >> 8);
        //            // G     
        //            pixelsForJpeg[2][x, y] = (byte)(color);
        //            // B    
        //        }
        //    }
        //    // Encode Image as JPEG using the FluxJpeg library  
        //    // and write to destination stream  
        //    var cm = new ColorModel { colorspace = ColorSpace.RGB };
        //    FluxJpeg.Core.Image jpegImage = new FluxJpeg.Core.Image(cm, pixelsForJpeg);
        //    var encoder = new JpegEncoder(jpegImage, 95, destinationStream);
        //    encoder.Encode();
        //}

        [Conditional("DEBUG")]
        protected static void DebugOutputMemoryUsage(string label = null)
        {
            var limit = DeviceStatus.ApplicationMemoryUsageLimit;
            var current = DeviceStatus.ApplicationCurrentMemoryUsage;
            var remaining = limit - current;
            var peak = DeviceStatus.ApplicationPeakMemoryUsage;
            var safetyMargin = limit - peak;
            if (label != null)
            {
                Debug.WriteLine(label);
            }
            Debug.WriteLine("Memory limit (bytes): " + limit);
            Debug.WriteLine("Current memory usage: {0} bytes ({1} bytes remaining)", current, remaining);
            Debug.WriteLine("Peak memory usage: {0} bytes ({1} bytes safety margin)", peak, safetyMargin);
        }

        #endregion

        #region Database Methods

        /// <summary>
        /// Loads the current playlist
        /// </summary>
        /// <param name="currentContext"></param>
        private void LoadCurrentPlayList(AudioStreamingDataContext currentContext)
        {
            PlaylistTrack[] playList;
            if (RadioSettings.Instance.AudioPlaybackMode == PlaybackMode.Streaming)
            {
                //The following sets up the 'stream' as a playlist
                playList = Repository.Configuration.Streams.Where(s => s == Repository.CurrentStream).ToPlaylist();
            }
            else
            {
                //The following creates a list of 'podcasts' as a playlist
                playList = currentContext.Playlists.OrderBy(p => p.CurrentPlaylistOrder)
                    .Select(p => new PlaylistTrack
                                     {
                                         Album = p.Album,
                                         Artist = p.Artist,
                                         Title = p.Title,
                                         AlbumArtUrl = p.AlbumArtUrl,
                                         Source = p.Source,
                                         IsStreaming = false,
                                         PlayerControls = EnabledPlayerControls.Pause | 
                                         EnabledPlayerControls.FastForward | 
                                         EnabledPlayerControls.Rewind 
                                     }).ToArray();
            }

            activeTrackList.Replace(playList);
        }

        ///// <summary>
        ///// Clears the current playlist
        ///// </summary>
        //private void ClearPlaylist()
        //{
        //    using (var wrapper = new MutexWrapper())
        //    {
        //        wrapper.Database.Playlists.DeleteAllOnSubmit(wrapper.Database.Playlists.ToArray());
        //        wrapper.Database.SubmitChanges();
        //    }
        //}

        /// <summary>
        /// Inserts a track into the history
        /// </summary>
        /// <param name="track"></param>
        /// <param name="currentContext"></param>
        private void AddTrackToPlaylist(PlaylistTrack track, AudioStreamingDataContext currentContext)
        {
            var myPlaylistTrack = new Playlist
                                      {
                Id = Guid.NewGuid(),
                Album = track.Album,
                Artist = track.Artist,
                AlbumArtUrl = track.AlbumArtUrl,
                CurrentPlaylistOrder = track.CurrentPlaylistOrder,
                Source = track.Source,
                Title = track.Title
            };

            currentContext.Playlists.InsertOnSubmit(myPlaylistTrack);
            currentContext.SubmitChanges();
        }

        ///// <summary>
        ///// Inserts a track into the history
        ///// </summary>
        ///// <param name="track"></param>
        ///// <param name="currentContext"></param>
        //private void RemoveTrackFromPlaylist(PlaylistTrack track, AudioStreamingDataContext currentContext)
        //{
        //    Playlist myPlaylistTrack = currentContext.Playlists.FirstOrDefault(t => t.Id == track.Id) ??
        //                               currentContext.Playlists.FirstOrDefault(t => t.Title == track.Title);
        //    currentContext.Playlists.DeleteOnSubmit(myPlaylistTrack);
        //    currentContext.SubmitChanges();
        //}
                
        /// <summary>
        /// Cleans up old tracks to make sure database doesn't get too big
        /// </summary>
        private void CleanupOldTracksFromPlaylist(AudioStreamingDataContext currentContext)
        {
            if (currentContext.Playlists.Count() > MaximumPlaylistTrackHistoryItemCount)
            {
                var playListItemsToRemove = currentContext.Playlists.Skip(MaximumPlaylistTrackHistoryItemCount).ToList();

                while (playListItemsToRemove.Any())
                {
                    currentContext.Playlists.DeleteOnSubmit(playListItemsToRemove.First());
                    currentContext.SubmitChanges();
                    //lets not delete this item more than once!
                    playListItemsToRemove.Remove(playListItemsToRemove.First());
                }

            }
        }

        /// <summary>
        /// Update tracks in the database
        /// </summary>
        private void UpdateTracksToDatabase()
        {
            using (var wrapper = new MutexWrapper())
            {
                CleanupOldTracksFromHistory(wrapper.Database);
                foreach (var track in RecentTracks.Take(MaximumPlaylistTrackHistoryItemCount))
                {
                    if (!TrackExists(track, wrapper.Database))
                    {
                        AddTrackToHistory(track, wrapper.Database);
                    }
                    else
                    {
                        UpdateTrackHistory(track, wrapper.Database);
                    }
                }
            }


        }
        
        /// <summary>
        /// Cleans up old tracks from history to make sure database doesn't get too big
        /// </summary>
        private void CleanupOldTracksFromHistory(AudioStreamingDataContext currentContext)
        {
            if (currentContext.TrackHistories.Count() > MaximumPlaylistTrackHistoryItemCount)
            {
                var trackHistoryItemsToRemove = currentContext.TrackHistories.Skip(MaximumPlaylistTrackHistoryItemCount).ToList();
                while (trackHistoryItemsToRemove.Any())
                {
                    currentContext.TrackHistories.DeleteOnSubmit(trackHistoryItemsToRemove.First());
                    currentContext.SubmitChanges();
                    //lets not delete this item more than once!
                    trackHistoryItemsToRemove.Remove(trackHistoryItemsToRemove.First());
                }

            }
        }
        
        /// <summary>
        /// Check if a track exists
        /// </summary>
        /// <param name="track"></param>
        /// <param name="currentContext"></param>
        /// <returns></returns>
        private bool TrackExists(TrackModel track, AudioStreamingDataContext currentContext)
        {
            return currentContext.TrackHistories.Any(t => t.TrackName == track.TrackName && t.Artist == track.Artist);
        }

        /// <summary>
        /// Inserts a track into the history
        /// </summary>
        /// <param name="track"></param>
        /// <param name="currentContext"></param>
        private void AddTrackToHistory(TrackModel track, AudioStreamingDataContext currentContext)
        {
            var myTrack = new TrackHistory
                              {
                Id = Guid.NewGuid(),
                Album = track.Album,
                Artist = track.Artist,
                CurrentlyPlaying = track.NowPlaying,
                LastPlayed = track.PlayedTime,
                TrackImageUrl = track.TrackImageUrl,
                TrackName = track.TrackName
            };

            currentContext.TrackHistories.InsertOnSubmit(myTrack);
            currentContext.SubmitChanges();
        }

        /// <summary>
        /// Updates the last played time for a track
        /// </summary>
        /// <param name="track"></param>
        /// <param name="currentContext"></param>
        private void UpdateTrackHistory(TrackModel track, AudioStreamingDataContext currentContext)
        {
            var currentTrack = currentContext.TrackHistories.Where(t => t.TrackName == track.TrackName && t.Artist == track.Artist);

            currentTrack.Single().LastPlayed = track.PlayedTime;
            currentTrack.Single().CurrentlyPlaying = track.NowPlaying;

            currentContext.SubmitChanges();
        }
        #endregion

        #region Events
        public event EventHandler TracksUpdated;
        public event EventHandler LiveTileUpdated;
        public void AddTrackToPlaylist(PlaylistTrack track)
        {
            using (var wrapper = new MutexWrapper())
            {
                CleanupOldTracksFromPlaylist(wrapper.Database);
                AddTrackToPlaylist(track,wrapper.Database);
            }
        }

        #endregion
    }
}
